/**
 * Copyright (c) 2011, www.quartzsource.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.quartzsource.core

import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.{ Map => MMap }
import scala.collection.mutable.ListBuffer

object Qpatch {

  type Fragment = (Int, Int)

  private class Buffer(size: Int) extends java.io.ByteArrayOutputStream(size) {

    def write(bin: Binary) {
      write(bin.toArray)
    }

    def write(pos: Int, bin: Binary) {
      val arr = bin.toArray
      count = pos
      write(arr, 0, arr.length)
    }

    def move(dest: Int, src: Int, count: Int) {
      Array.copy(buf, src, buf, dest, count)
    }

    def readInt(pos: Int): Int = {
      Parsers.readInt(buf, pos)
    }

    def slice(start: Int, count: Int): Array[Byte] = {
      require(start >= 0)
      require(count >= 0)
      val result = new Array[Byte](count)
      Array.copy(buf, start, result, 0, count)
      result
    }
  }

  /**
   * This attempts to apply a series of patches in time proportional to
   * the total size of the patches, rather than patches * len(text). This
   * means rather than shuffling strings around, we shuffle around
   * pointers to fragments with fragment lists.
   */
  def patches(a: Binary, bins: List[Binary]): Binary = {
    require(a != null)
    require(bins != null)
    if (bins.isEmpty) return a
    val plens = bins.map(_.size)
    val pl = plens.reduceLeft((b, i) => b + i)
    val bl = a.size + pl
    val tl = bl + bl + pl //enough for the patches and two working texts
    var (b1, b2) = (0, bl)

    val m = new Buffer(tl)
    //load our original text
    m.write(a)
    var frags = ListBuffer[Fragment]((a.size, b1))

    //copy all the patches into our segment
    var pos = b2 + bl
    val allPathes = bins.reduceLeft((b, a) => b ++ a)
    m.write(pos, allPathes)

    def collect(buffer: Int, list: List[Fragment]): Fragment = {
      var buf = buffer
      val start = buffer
      list.foreach(t => {
        val (l, p) = t
        m.move(buf, p, l)
        buf += l
      })
      (buf - start, start)
    }

    for (plen <- plens) {
      //if our list gets too long, execute it
      if (frags.size > 128) {
        val temp = b2
        b2 = b1
        b1 = temp
        frags = ListBuffer[Fragment](collect(b1, frags.toList))
      }

      val newFragments = ListBuffer[Fragment]()
      val end = pos + plen
      var last = 0
      var break = false
      while (pos < end) {
        val p1 = m.readInt(pos)
        val p2 = m.readInt(pos + 4)
        val l = m.readInt(pos + 8)
        pull(newFragments, frags, p1 - last) // what didn't change
        pull(ListBuffer[Fragment](), frags, p2 - p1) // what got deleted
        newFragments += ((l, pos + 12)) // what got added
        pos += l + 12
        last = p2
      }
      newFragments ++=: frags
    }

    val t = collect(b2, frags.toList)
    Binary(m.slice(t._2, t._1).toArray)
  }

  //pull l bytes from src
  private def pull(dst: ListBuffer[Fragment], src: ListBuffer[Fragment], length: Int) {
    var l = length
    while (l != 0) {
      val f = src.remove(0)
      if (f._1 > l) {
        //do we need to split?
        ((f._1 - l, f._2 + l)) +=: src
        dst += ((l, f._2))
        return
      }
      dst += f
      l -= f._1
    }
  }

}
